package phaseutil

import (
	"context"
	"fmt"
	"strings"

	"github.com/pkg/errors"
	agentv1beta1 "gopkg.openfuyao.cn/bkeagent/api/v1beta1"
	"gopkg.openfuyao.cn/bkecommon/cluster/api/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	kerrors "k8s.io/apimachinery/pkg/util/errors"
	"sigs.k8s.io/controller-runtime/pkg/client"

	bkev1beta1 "gopkg.openfuyao.cn/cluster-api-provider-bke/api/v1beta1"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/command"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils"
	labelhelper "gopkg.openfuyao.cn/cluster-api-provider-bke/utils/label"
)

func LogCommandFailed(cmd agentv1beta1.Command, failedNods []string, log *bkev1beta1.BKELogger, reason string) (commandErrs map[string][]string, err error) {
	if len(failedNods) == 0 {
		return nil, nil
	}
	commandErrs = make(map[string][]string)
	errs := make([]error, 0)
	for _, node := range failedNods {
		if v, ok := cmd.Status[node]; ok {
			for _, condition := range v.Conditions {
				if condition.Status == metav1.ConditionFalse && (condition.StdErr != nil || len(condition.StdErr) > 0) {
					errInfo := fmt.Sprintf("Node %q, Command %s, sub ID %q, err: %s", node, utils.ClientObjNS(&cmd), condition.ID, condition.StdErr[len(condition.StdErr)-1])
					commandErrs[node] = append(commandErrs[node], errInfo)
					errs = append(errs, errors.New(errInfo))
					// 输出最后一次运行的错误信息
					log.Error(reason, errInfo)
				}
			}
		}
	}
	return commandErrs, kerrors.NewAggregate(errs)
}

func MarkNodeStatusByCommandErrs(bkeCluster *bkev1beta1.BKECluster, commandErrs map[string][]string) {
	if len(commandErrs) == 0 || commandErrs == nil || bkeCluster == nil {
		return
	}
	for nodeIp, errInfos := range commandErrs {
		bkeCluster.SetNodeStateMessage(nodeIp, fmt.Sprintf("%v", errInfos))
	}
}

func LogCommandInfo(cmd agentv1beta1.Command, log *bkev1beta1.BKELogger, reason string) {
	for nodeInfo, nodeCondition := range cmd.Status {
		for _, condition := range nodeCondition.Conditions {
			if condition.Status == metav1.ConditionTrue && (condition.StdOut != nil || len(condition.StdOut) > 0) {
				log.Info(reason, fmt.Sprintf("Node %q, Command %s, sub ID %q, info: %v", nodeInfo, utils.ClientObjNS(&cmd), condition.ID, condition.StdOut))
			}
		}
	}
}

func GetMasterInitCommand(ctx context.Context, c client.Client, bkeCluster *bkev1beta1.BKECluster) (*agentv1beta1.Command, error) {
	commandsLi := agentv1beta1.CommandList{}
	filters := GetListFiltersByBKECluster(bkeCluster)

	if err := c.List(ctx, &commandsLi, filters...); err != nil {
		return nil, err
	}

	for _, cmd := range commandsLi.Items {
		if labelhelper.HasLabel(&cmd, command.MasterInitCommandLabel) {
			return &cmd, nil
		}
	}
	return nil, errors.New("master init command not found")
}

func GetMasterJoinCommand(ctx context.Context, c client.Client, bkeCluster *bkev1beta1.BKECluster) (*agentv1beta1.Command, error) {
	commandsLi := agentv1beta1.CommandList{}
	filters := GetListFiltersByBKECluster(bkeCluster)

	if err := c.List(ctx, &commandsLi, filters...); err != nil {
		return nil, err
	}

	for _, cmd := range commandsLi.Items {
		if labelhelper.HasLabel(&cmd, command.MasterJoinCommandLabel) {
			return &cmd, nil
		}
	}
	return nil, errors.New("master join command not found")
}

func GetWorkerJoinCommand(ctx context.Context, c client.Client, bkeCluster *bkev1beta1.BKECluster) (*agentv1beta1.Command, error) {
	commandsLi := agentv1beta1.CommandList{}
	filters := GetListFiltersByBKECluster(bkeCluster)

	if err := c.List(ctx, &commandsLi, filters...); err != nil {
		return nil, err
	}

	for _, cmd := range commandsLi.Items {
		if labelhelper.HasLabel(&cmd, command.WorkerJoinCommandLabel) {
			return &cmd, nil
		}
	}
	return nil, errors.New("worker join command not found")
}

func GetNodeIPFromCommandWaitResult(result string) string {
	nodeInfo := strings.Split(result, "/")
	nodeIP := nodeInfo[0]
	if len(nodeInfo) == 2 {
		nodeIP = nodeInfo[1]
	}
	return nodeIP
}

func GetNotSkipFailedNode(bkeCluster *bkev1beta1.BKECluster, failedNodesInfo []string) int {
	nodesStatus := bkeCluster.Status.NodesStatus
	nodeStatusByIP := make(map[string]*v1beta1.NodeState, len(nodesStatus))
	for i := range nodesStatus {
		node := &nodesStatus[i]
		if node.Node.IP != "" {
			nodeStatusByIP[node.Node.IP] = node
		}
	}

	notSkipCnt := 0
	for _, node := range failedNodesInfo {
		nodeIP := GetNodeIPFromCommandWaitResult(node)
		if nodeState, exists := nodeStatusByIP[nodeIP]; exists {
			if !nodeState.NeedSkip {
				notSkipCnt++
			}
		}
	}

	return notSkipCnt
}
